package ru.compscicenter.databases.zaikina.nodes;

import org.json.simple.JSONArray;
import org.json.simple.JSONAware;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import ru.compscicenter.databases.zaikina.common.*;
import ru.compscicenter.databases.zaikina.common.commands.*;
import ru.compscicenter.databases.zaikina.common.model.Node;
import ru.compscicenter.databases.zaikina.common.model.NodeConfig;
import ru.compscicenter.databases.zaikina.common.model.WordWithReferences;
import ru.compscicenter.databases.zaikina.httpserver.CommandTypes;
//import sun.invoke.empty.Empty;

import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;


public class FilesNode extends AbstractManagedObject {
    private FilesStorage storage;
    private final NodeConfig config;
    private final Integer currentNodeId;
    private HashMap<Integer, CommandArray> commandHashMap = new HashMap<Integer, CommandArray>();

    IOProcessor<CommandArray> io = new IOProcessor<CommandArray>() {
        @Override
        protected CommandArray parseItem(String str) {
            return CommandArray.parse(str);
        }
    };

    private static class CommandArray implements JSONAware {
        private ArrayList<FullWordsCommand> commands = new ArrayList<FullWordsCommand>();

        public String toJSONString() {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("commands", commands);
            return jsonObject.toString();
        }

        public static CommandArray parse(String jsonStr) {
            JSONObject object = (JSONObject) JSONValue.parse(jsonStr);
            JSONArray arr = (JSONArray) JSONValue.parse(object.get("commands").toString());
            CommandArray result = new CommandArray();
            for (int i = 0; i < arr.size(); ++i) {
                result.commands.add(FullWordsCommand.parse(arr.get(i).toString()));
            }
            return result;
        }
    }

    private final String pathToDump;

    public FilesNode(NodeConfig config, Integer currentNodeId) {
        this.storage = new FilesStorage(currentNodeId);
        this.storage.load();
        this.config = config;
        this.currentNodeId = currentNodeId;
        this.pathToDump = currentNodeId + File.separator + "command_dump.txt";

    }

    public void load() {
        this.commandHashMap = io.loadObjectsFromDump(pathToDump);
    }

    public EmptyCommand process(StringListCommand command) {
        if (command.getType().equals(SysCommandType.AddFiles)) {
            System.out.println("AddFiles. Process...");
            ArrayList<WordWithReferences> arr = storage.add(command.getBody());

            for (WordWithReferences word : arr) {
                System.out.println(word.toJSONString());
            }
            formCommands(arr, SysCommandType.AddWords);
            sendToVocabulary();
            io.writeObjectsToDump(pathToDump, commandHashMap);
            return new EmptyCommand(SysCommandType.Success);
        }
        return null;
    }

    public FullDocumentsCommand process(FullWordsCommand command) {
        if (command.getType().equals(SysCommandType.GetFilesByWords)) {
            ArrayList<Integer> ids = new ArrayList<Integer>();
            boolean flag = true;
            for (WordWithReferences word : command.getBody()) {
                if (flag) {
                    ids.addAll(word.getReferences());
                    flag = false;
                } else {
                    ids.retainAll(word.getReferences());
                }
            }
            return new FullDocumentsCommand(SysCommandType.FullDocumentsAnswer, storage.get(ids), -1);
        }
        return null;
    }

    public FilesStorage getStorage() {
        return storage;
    }

    public FilesNode clone() {
        FilesNode copy = new FilesNode(config, currentNodeId);
        copy.storage = storage.clone();
        copy.commandHashMap.putAll(commandHashMap);
        return copy;
    }

    public void commit() {
        storage.commit();
    }

    private void sendToVocabulary() {
        for (Map.Entry<Integer, CommandArray> commandEntry : commandHashMap.entrySet()) {
            for (int i = 0; i < commandEntry.getValue().commands.size(); ++i) {
                if (sendToVocabulary(commandEntry.getKey(), commandEntry.getValue().commands.get(i))) {
                    commandEntry.getValue().commands.remove(i);
                    --i;
                }
            }
        }
    }

    private boolean sendToVocabulary(Integer receiver, FullWordsCommand command) {
        EmptyCommand result = null;
        Node node = NodeMain.config.getNodeById(receiver);
        Socket socket;
        try {
            //socket = new Socket("127.0.0.1", 10003);
            socket = new Socket(node.getAddress(), node.getPort());
            ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
            ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());

            System.out.println(command.toJSONString());

            out.writeObject(command.toJSONString());
            out.flush();
            String answer = readAnswer(in);

            System.out.println(answer);

            in.close();
            out.close();
            socket.close();

            result = EmptyCommand.parse(answer);
        } catch (IOException ex) {
            System.out.println("Node: " + node.getNodeId() + " at " + node.getAddress() + ":" + node.getPort() + " unreachable. Command will be send later.");
            //ex.printStackTrace();
        }
        if (result != null) {
            return result.getType().equals(SysCommandType.Success);
        }
        return false;
    }

    private void formCommands(ArrayList<WordWithReferences> words, Integer commandType) {
        HashMap<Integer, ArrayList<WordWithReferences>> wordHashMap = new HashMap<Integer, ArrayList<WordWithReferences>>();
        for (WordWithReferences word : words) {
            Node node = config.getVocabularyNode(word.getObj().getKey());
            if (node == null) {
                continue;
            }

            if (wordHashMap.containsKey(node.getNodeId())) {
                wordHashMap.get(node.getNodeId()).add(word);
            } else {
                ArrayList<WordWithReferences> list = new ArrayList<WordWithReferences>();
                list.add(word);
                wordHashMap.put(node.getNodeId(), list);
            }
        }

        for (Integer id : wordHashMap.keySet()) {
            if (!commandHashMap.containsKey(id)) {
                CommandArray arr = new CommandArray();
                arr.commands.add(new FullWordsCommand(commandType, wordHashMap.get(id), -1));
                commandHashMap.put(id, arr);
            } else {
                commandHashMap.get(id).commands.add(new FullWordsCommand(commandType, wordHashMap.get(id), -1));
            }
        }
    }

    private String readAnswer(ObjectInputStream in) throws IOException {
        String text;
        try {
            text = (String) in.readObject();
        } catch (ClassNotFoundException ex) {
            return ex.getMessage();
        }
        return text;
    }

    public Integer getNodeId() {
        return currentNodeId;
    }
}
